/*
 * http_c.cpp
 *
 *  Created on: Oct 24, 2013
 *      Author: killerg
 */

#include "http_c.h"
#include "util.h"
#include <string.h>
#include <event2/buffer.h>

static void http_request_done(struct evhttp_request *req, void *ctx) {

    http_client::request_t* request = (http_client::request_t*)ctx;
    http_client* cli = request->cli;
    http_client::host_t* host = cli->get_host(request->host.c_str(), request->port);

    host->querying = false;

    if (request->cb) {
        if (NULL == req) {
            int errcode = EVUTIL_SOCKET_ERROR();
            UTIL_LOG(E, "socket error = %s (%d)\n", evutil_socket_error_to_string(errcode), errcode);

            request->cb(errcode, std::string(""));
        } else {
            int code = evhttp_request_get_response_code(req);
            auto input = evhttp_request_get_input_buffer(req);

            std::string body;

            while (evbuffer_get_length(input)) {
                int n;
                char cbuf[129];
                n = evbuffer_remove(input, cbuf, sizeof(cbuf));
                cbuf[n] = 0;
                if (n > 0)
                    body += cbuf;
            }

            request->cb(code, body);
        }
    }

    cli->dealloc_request(request);

    host->try_do_request();
    --cli->_pending_num;
}

bool http_client::host_t::add_request(request_t* request) {
    req_list.push_back(request);
    return true;
}

bool http_client::host_t::try_do_request() {

//  if (querying)
//      return false;

    if (req_list.empty())
        return false;

    request_t* request = req_list.front();
    req_list.pop_front();

    auto req = evhttp_request_new(http_request_done, request);
    if (req == NULL) {
    	UTIL_LOG(E, "evhttp_request_new() failed\n");
        request->cb(-1, std::string());
        request->cli->dealloc_request(request);
        return false;
    }

    auto output_headers = evhttp_request_get_output_headers(req);
    evhttp_add_header(output_headers, "Host", request->host.c_str());

    evhttp_cmd_type method = EVHTTP_REQ_GET;
    if (!request->body.empty()) {
        method = EVHTTP_REQ_POST;
        auto out_buffer = evhttp_request_get_output_buffer(req);
        evbuffer_add(out_buffer, request->body.c_str(), request->body.length());
    }

    auto r = evhttp_make_request(conn, req, method, request->uri.c_str());
    if (r != 0) {
    	UTIL_LOG(E, "evhttp_make_request() failed\n");
        request->cb(-1, std::string());
        request->cli->dealloc_request(request);
        return false;
    }

    querying = true;
    return true;

}

http_client::host_t* http_client::get_host(const char* host, int port) {
    std::string head_part = fmt_str("%s:%d", host, port);

    host_t* phost = head_host_map.get(head_part);
    if (NULL != phost)
        return phost;

    phost = new host_t;
    phost->head_part = head_part;
    phost->querying = false;
    phost->conn = evhttp_connection_base_new(base, dns_base, host, port);

    if (NULL == phost->conn) {
        delete phost;
        phost = NULL;
    }

    head_host_map.set(head_part, phost);
    conn_host_map.set(phost->conn, phost);

    return phost;
}

void http_client::dealloc_request(request_t* &request) {
    if (NULL == request)
        return;

    delete request;
    request = NULL;
}

http_client::request_t* http_client::alloc_request(
        const char* host,
        int port,
        const char* uri,
        const char* body,
        http_client* cli,
        client_cb_t cb) {

    request_t* request = new request_t;

    request->host = host;
    request->uri = uri;
    request->port = port;
    request->body = (body == NULL ? "" : body);
    request->cb = cb;
    request->cli = cli;

    return request;
}

bool http_client::do_query(
        const char* url,
        const char* body,
        client_cb_t cb) {

    auto http_uri = evhttp_uri_parse(url);
    if (http_uri == NULL) {
        perror("url error");
        return false;
    }

    std::string path = evhttp_uri_get_path(http_uri);
    if (path == "") {
        path = "/";
    }

    char uri[256];
    const char* query = evhttp_uri_get_query(http_uri);
    if (query == NULL) {
        snprintf(uri, sizeof(uri) - 1, "%s", path.c_str());
    } else {
        snprintf(uri, sizeof(uri) - 1, "%s?%s", path.c_str(), query);
    }

    uri[sizeof(uri) - 1] = '\0';

    std::string shost = evhttp_uri_get_host(http_uri);
    int port = evhttp_uri_get_port(http_uri);
    std::string suri = uri;

    if (port <= 0)
        port = 80;

    evhttp_uri_free(http_uri);

    host_t* host = get_host(shost.c_str(), port);
    if (NULL == host)
        return false;

    request_t* request = alloc_request(
            shost.c_str(),
            port,
            suri.c_str(),
            body,
            this,
            cb);

    if (!host->add_request(request)) {
        dealloc_request(request);
        return false;
    }

    host->try_do_request();
    ++_pending_num;

    return true;
}

bool http_client::init() {
    base = event_base_new();
    if (NULL == base) {
        perror("event_base_new NULL");
        return false;
    }
    dns_base = evdns_base_new(base, 1);
    if (NULL == dns_base) {
        perror("evdns_base_new NULL");
        return false;
    }

    return true;
}

void http_client::destroy() {
    head_host_map.for_each([](std::string head, host_t* &host){
        evhttp_connection_free(host->conn);
        host->conn = NULL;

        delete host;
        host = NULL;
    });
    head_host_map.clear();

    if (NULL != dns_base) {
        evdns_base_free(dns_base, 0);
        dns_base = NULL;
    }
    if (NULL != base) {
        event_base_free(base);
        base = NULL;
    }
}

void http_client::update() {
    event_base_loop(base, EVLOOP_NONBLOCK);
}
